#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <jsoncpp/json/json.h>
#include <signal.h>

#include "compile.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"

namespace ns_compile_and_run
{
    using namespace ns_compile;
    using namespace ns_runner;
    using namespace ns_log;
    using namespace ns_util;

    // 该类负责编译和运行模块的整合
    class CompileAndRun
    {
    public:
        /******************************
         * 函数功能: 实现编译和运行的整合，将运行结果/错误信息返回
         * in_json: {"code":"代码";"input":"提交的样例";"cpulimit":"CPU限制资源";"memlimit":"内存限制资源"}
         * out_json:{"status":"状态码";"result":"请求结果"();"stdout":"执行结果"(可填);"stderr":"错误结果"(可填，编译错误/运行错误)}
         * 规定：
         * 1. status > 0: 运行错误
         * 2. status == 0: 运行正确
         * 3. status < 0: 未知错误（内部错误）/编译错误
         *******************************/
        static void Start(const std::string &in_json, std::string *out_json)
        {
            // 统一处理的，使用goto语句
            int status_code = 0;  // 状态码
            std::string filename; // 为了使用goto，将源文件名放置在前面
            int run_result = 0;   // 程序运行的特殊结果

            // 1、获取用户的传入信息：用户的代码；上层的测试用例、cpu限制、内存限制
            Json::Value in_value;
            Json::Reader json_reader;
            json_reader.parse(in_json, in_value);
            std::string user_code = in_value["code"].asString(); // user_code:一定是具有格式的
            std::string user_input = in_value["input"].asString();
            int cpu_limit = in_value["cpulimit"].asInt();
            int mem_limit = in_value["memlimit"].asInt();

            if (user_code.empty())
            {
                status_code = -1; // 表示用户提交的代码为空
                goto END;
            }

            // 2、构建源文件
            // 2.1：获取一个具有唯一性的文件名
            filename = FileUtil::UniqFileName();
            // 2.2：写入源文件中
            if (!FileUtil::WriteFile(PathSpliceUtil::Src(filename), user_code))
            {
                status_code = -2; // 表示未知的错误
                goto END;
            }

            // 3、编译源文件
            if (!Compiler::Compile(filename)) // 只需要传入文件名即可
            {
                // 编译失败 - 让用户知道
                status_code = -3; // 代表编译失败
                goto END;
            }

            // 4、执行代码
            run_result = Runner::run(filename, cpu_limit, mem_limit);
            // 这里就不再使用goto了，因为只会进入一层
            if (run_result < 0) // run功能中的内部错误
            {
                status_code = -2; // 表示未知的错误
                LOG(Warning, "[%s]完成一个执行程序请求, 但是发生了内部错误", filename.c_str());
            }
            else if (run_result == 0)
            {
                status_code = 0; // 运行成功
                LOG(Info, "[%s]完成一个执行程序请求", filename.c_str());
            }
            else // run_result > 0
            {
                status_code = run_result; // 异常错误
            }

            // 处理状态码，将状态码转化为描述
        END:
            // a. status: status_code
            // b. result: result
            std::string result = StatuToDesc(status_code, filename); 
            //我们将信号的信息都放在了result

            // 5. 序列化
            Json::Value out_value;
            out_value["status"] = status_code;
            out_value["result"] = result;
            if (status_code == 0)
            {
                // 运行成功
                //  c. stdout
                //  d. stderr
                std::string code_stdout, code_stderr;
                FileUtil::ReadFile(PathSpliceUtil::Stdout(filename), &code_stdout);
                FileUtil::ReadFile(PathSpliceUtil::Stderr(filename), &code_stderr);
                out_value["stdout"] = code_stdout;
                out_value["stderr"] = code_stderr;
            }
            Json::StyledWriter json_write;
            *out_json = json_write.write(out_value);

            // 清除生成的可执行文件
            RemoveTempFile(filename);
        }

    private:
        /******************************
         * 功能：将对应的状态码转化为状态码的描述
         * 例如：0：运行成功……
         * < 0: 表示内部错误
         * == 0: 表示运行成功
         * > 0: 表示运行异常
         ******************************/
        static std::string StatuToDesc(int statuscode, const std::string &filename)
        {
            std::string desc;
            switch (statuscode)
            {
            case 0: // 运行成功
                desc = "The code ran successfully.";
                break;
            case -3:
                // desc = "代码在编译时期发生错误";
                if (!FileUtil::ReadFile(PathSpliceUtil::Compilerr(filename), &desc))
                {
                    desc = "The server has encountered an unknown error.";
                }
                break;
            case -2:
                desc = "The server has encountered an unknown error.";
                break;
            case -1:
                desc = "The file submitted by the user is empty.";
                break;
            case SIGABRT: // 6号信号
                desc = "The user has exceeded the memory limit.";
                break;
            case SIGXCPU: // 24
                desc = "The code has exceeded the time limit for execution.";
                break;
            case SIGSEGV: // 11
                desc = "segmentation fault";
                break;
            case SIGFPE: // 8
                desc = " floating-point underflow";
                break;
            // 可扩展
            default:
                desc = "other unknow error: " + std::to_string(statuscode);
                break;
            }
            return desc;
        }

    private:
        /******************************
         * 功能：将对应的状态码转化为状态码的描述
         * 例如：0：运行成功……
         * < 0: 表示内部错误
         * == 0: 表示运行成功
         * > 0: 表示运行异常
         ******************************/
        static void RemoveTempFile(const std::string &filename)
        {
            FileUtil::UnlinkFile(PathSpliceUtil::Src(filename));
            FileUtil::UnlinkFile(PathSpliceUtil::Exe(filename));
            FileUtil::UnlinkFile(PathSpliceUtil::Compilerr(filename));
            FileUtil::UnlinkFile(PathSpliceUtil::Stdin(filename));
            FileUtil::UnlinkFile(PathSpliceUtil::Stdout(filename));
            FileUtil::UnlinkFile(PathSpliceUtil::Stderr(filename));
        }
    };
}